local L = Rock("LibRockLocale-1.0"):GetTranslationNamespace("TrainingWheels")
for i,v in pairs(TrainingWheels_Locale) do
	L:AddTranslations(v.locale, function() return v end)
end

TrainingWheels = Rock:NewAddon("TrainingWheels", "LibRockConsole-1.0", 
								"LibRockConfig-1.0", "LibRockDB-1.0", 
								"LibRockTimer-1.0", "LibRockEvent-1.0", 
								"LibRockHook-1.0", "FuBarPlugin-2.0")

local newList, del, newDict, newSet, unpackDictAndDel, unpackListAndDel = 
							Rock:GetRecyclingFunctions("TrainingWheels", "newList", 
							"del", "newDict", "newSet", "unpackDictAndDel", 
							"unpackListAndDel")

local self = TrainingWheels
local VERSION = tonumber(("$Revision: 60000 $"):match("%d+"))
self.revision = VERSION
self.date = ("$Date: 2008-10-20 14:04:19 -0400 (Sat, 20 Oct 2008) $"):match("%d%d%d%d%-%d%d%-%d%d")
self.svnURL = "$URL: svn://dev.wowace.com/wowace/trunk/TrainingWheels/TrainingWheels.lua $"
self.spellVersion = 1
local _,_,source = self.svnURL:find("^.+/(.+)/TrainingWheels/")
self.source = source:gsub("^trunk$", "unstable")
self.version = "1.0r" .. VERSION .. "s"..self.spellVersion .. " (".. self.source ..")"


-- debug code
self.DEBUG = false

--Fubar
self.hasIcon = "Interface\\Icons\\INV_Misc_Gear_04"
self.hasNoColor  = true
self.cannotDetachTooltip = true
self.blizzardTooltip = true
self.hideWithoutStandby = true
self.title = L["Addon title"]
self.independentProfile = true
	
--Player Info
self.playerName = UnitName("player")
self.realmName = GetRealmName()
local _, class = UnitClass("player")
self.className = class

self.includedLists = {}
self.playerEquipment = {}
self.snapshots = {}
self.haste     = 0
self.myGCD     = 1.5


--Refresh options
self.thisFrameRefreshed = false
self.nextFrameRefresh = false
self.time = GetTime()
self.lastRefresh = 0
self.refreshList = {}

--For dialogs
self.configShow = nil


-- Permissible unitIDs - 
self.unitIDList = {L["Player"], L["Target"], L["Pet"], L["Focus"], L["Mouseover"]}
-- expand to include Targettarget or validate extensions with regex

--Version
Rock("LibRockComm-1.0"):AddAddonVersionReceptor(function (...)
	TrainingWheels:OnVersionReceive(...)
end)

function self:OnVersionReceive(player, addon, version)
	if (addon == "TrainingWheels") then
		if (version == true) then
			version = "Unknown version"
		elseif (version == false) then
			version = "Not loaded"
		end
		self:Print("Version ("..player.."): "..version)
	end
end

function self:QueryVersion(target)
	if (target:upper() == "TARGET") then target = UnitName("target") end
	if (target ~= nil) then
		target = strtrim(target)
		if (target ~= "") then
			local distribution = target:upper()
			if (distribution ~= "GROUP" and distribution ~= "GUILD") then
				distribution = "WHISPER"
			else
				target = nil
			end
			Rock("LibRockComm-1.0"):QueryAddonVersion("TrainingWheels", distribution, target)
		end
	end
end

self.globalStrings = {
	["SPELL_CAST_TIME_SEC"] = gsub(SPELL_CAST_TIME_SEC, "%%%.%d[fg]", "(.+)"),
	["SPELL_CAST_TIME_MIN"] = gsub(SPELL_CAST_TIME_MIN, "%%%.%d[fg]", "(.+)"),
	["SPELL_RECAST_TIME_MIN"] = gsub(SPELL_RECAST_TIME_MIN, "%%%.%d[fg]", "(.+)"),
	["SPELL_RECAST_TIME_SEC"] = gsub(SPELL_RECAST_TIME_SEC, "%%%.%d[fg]", "(.+)"),
}

StaticPopupDialogs["TrainingWheels_ConfirmDeleteSpellList"] = {
	text = L["Confirm delete spell list"],
	button1 = YES,
	button2 = NO,
	OnAccept = function(val)
		TrainingWheels:DeleteSpellList(val, true)
	end,
	OnShow = function()
		TrainingWheels.configShow = Rock("LibRockConfig-1.0").base:IsShown()
		if (TrainingWheels.configShow) then
			Rock("LibRockConfig-1.0").base:SetFrameStrata("HIGH")
		end
	end,
	onHide = function()
		if (TrainingWheels.configShow) then
			Rock("LibRockConfig-1.0").base:SetFrameStrata("FULLSCREEN_DIALOG")
		end
	end,
	timeout=0,
	exclusive=1,
	whileDead = 1,
	hideOnEscape = 1,
}
StaticPopupDialogs["TrainingWheels_ConfirmLoadSnapshot"] = {
	text = L["Confirm load snapshot"],
	button1 = YES,
	button2 = NO,
	OnAccept = function(val)
		TrainingWheels:LoadSnapshot(val, true)
	end,
	OnShow = function()
		TrainingWheels.configShow = Rock("LibRockConfig-1.0").base:IsShown()
		if (TrainingWheels.configShow) then
			Rock("LibRockConfig-1.0").base:SetFrameStrata("HIGH")
		end
	end,
	onHide = function()
		if (TrainingWheels.configShow) then
			Rock("LibRockConfig-1.0").base:SetFrameStrata("FULLSCREEN_DIALOG")
		end
	end,
	timeout=0,
	exclusive=1,
	whileDead = 1,
	hideOnEscape = 1,
};

self.options = {
	type = 'group',
	name = L["Addon title"],
	desc = L["Addon description"],
	icon = "Interface\\Icons\\INV_Misc_Gear_04",
	handler = TrainingWheels,
	args = {
	 -- basic arg initialization
		{
			type = 'number',
			name = L["Leeway"],
			desc = L["Leeway description"],
			get = function() return self.db.profile.leeway end,
			set = function(v) self.db.profile.leeway = v end,
			min = 0.0,
			max = 1.5,
			step = 0.1,
			order = 1.5,
		},
		{
			type = 'number',
			name = L["Latency"],
			desc = L["Latency description"],
			get = function() return self.db.profile.latency * 1000 end,
			set = function(v) self.db.profile.latency = v / 1000 end,
			min = 0,
			max = 1500,
			step = 100,
			order = 1,
		},
		{
			type = 'number',
			name = L["Maximum spells"],
			desc = L["Maximum spells description"],
			get = function() return self.db.profile.maxSpells end,
			set = function(v) self.db.profile.maxSpells = v end,
			min = 1,
			max = 20,
			step = 1,
			order = 2,
		},
		{
			type = 'boolean',
			name = L["Validate spells"],
			desc = L["Validate spells description"],
			get = function() return self.db.profile.validateSpells, true end,
			set = function(v) self.db.profile.validateSpells = v end,
			order = 2.5,
		},
		{
			type = 'boolean',
			name = L["Show labels"],
			desc = L["Show labels description"],
			get = function() return self.db.profile.showLabels, true end,
			set = function(v) self.db.profile.showLabels = v end,
			order = 2.75,
		},
		{
			type = 'execute',
			name = L["Move spell frame"],
			desc = L["Move spell frame description"],
			func = function () self.SpellFrame:ToggleMoving() end,
			buttonText = function () if (not self.SpellFrame:IsMoving()) 
						     then return L["Move spell frame"] 
						     else return L["Lock spell frame"]
						     end end,
			order = 3,
		},	
		{
    		type = 'range',
          	name = L["Alpha value"],
  	 		desc = L["Alpha value description"],
  	 		get = function() return self.db.profile.alpha end,
    		set = function(v)    self.db.profile.alpha = v end,
    		min = 0,
    		max = 1, -- since percentage, max is 100%,
    		step = 0.05, -- every 5%
    		isPercent = true,
		} ,
		
		-- enter size change code here
		
		{
			type = 'execute',
			name = L["Show version"],
			desc = L["Show version description"],
			func = "ShowVersion",
			buttonText = L["Show version"],
			order = 4,
		},
		{
			type = 'text',
			name = L["Query version"],
			desc = L["Query version description"], 
			get = false,
			set = "QueryVersion",
			validate = function(val)
				if self.EmptyOrWhiteSpace(val) then
					return false, L["Please type a valid name."]
				end
			end,
			usage = L["Query version usage"],
			order = 5,
		},
		{
			type = 'group',
			name = L["Spell list"],
			desc = L["Spell list description"],
			order = 5,
			args = function()
				
				local output = {
								{
									type = 'string',
									name = L["Add spell"],
									desc = L["Add spell description"],
									get = false,
									set = "AddSpell",
									validate = function(value)
										if (self.EmptyOrWhiteSpace(value)) then
											return false, L["Please type a valid name."]
										else 
											for i,v in pairs(self.db.char.spellList) do
												if (v.name == value) then
													return false, L["Please type a unique name."]
													
												end
											end
											return true
										end
									end,
									usage = L["<Spell Name (Modifiers)>"],
									order = 1,
								},
								{
									type = 'string',
									name = L["Save spell list"],
									desc = L["Save spell list description"],
									get = false,
									set = "SaveSpellList",
									validate = function(value)
										if (self.EmptyOrWhiteSpace(value)) then
											return false, L["Please type a valid name."]
										else 
											for i,v in pairs(self.db.profile.spellLists) do
												if (v.name == value) then
													return false, L["Please type a unique name."]
												end
											end
											return true
										end
									end,
									usage = L["Spell list usage"],
									order = 2,
								},
								{
									type = 'execute',
									name = L["Clear spell list"],
									desc = L["Clear spell list description"],
									func = "ClearSpellList",
									buttonText = L["Clear"],
									disabled = function() return #self.db.char.spellList == 0 end,
									confirmText = L["Confirm"] .. L["Clear"],
									order = 3,
								},
								{
									type = 'choice',
									name = L["Load spell list"],
									desc = L["Load spell list description"],
									get = function() return "" end,
									set = "LoadSpellList",
									choices = "GetSpellLists",
									disabled = "~HasSpellLists",
									order = 4,
								},
								{
									type = 'choice',
									name = L["Delete spell list"],
									desc = L["Delete spell list description"],
									get = function() return "" end,
									set = "DeleteSpellList",
									choices = function() return TrainingWheels:GetSpellLists(true) end,
									disabled = function() return not TrainingWheels:HasSpellLists(true) end,
									order = 5,
								},
								{
									type = 'choice',
									name = L["Load snapshot"],
									desc = L["Snapshot description"],
									get = function() return "" end,
									set = "LoadSnapshot",
									choices = "GetSnapshots",
									choiceType = "dict",
									order = 6,
								},
							} -- end output
				
				for i,v in pairs(TrainingWheels.db.char.spellList) do

					--This sucks (the Dictionary method that is)
					local curSpell = {
						type = 'group',
						name = v.name,
						desc = L["Modify the settings for "]..v.name..".",
						child_passValue = i,
						order = 200 - v.priority,
						args = function()

							local curSpellOutput =
							 --begin storage of curSpellOutput
                             {
								{
									type = "boolean",
									name = L["Enabled"],
									desc = L["Spell enabled description"],
									get = function(i) return self.db.char.spellList[i].enabled end,
									set = function(i,v) self.db.char.spellList[i].enabled = v end,
									order = 2,
								},
								{
									type = "execute",
									name = L["Delete spell"],
									desc = L["Delete spell description"],
									func = "DeleteSpell",
									confirmText = L["Confirm"] .. L["Delete"],
									buttonText = L["Delete spell"],
									order = 1,
								},
								{
									type = "string",
									desc = L["Name display description"],
									name = L["Name"],
									get = function(i) return self.db.char.spellList[i].name end,
									set = function(i,v) self.db.char.spellList[i].name = v end,
									validate = function(passValue, value)
										if (self.EmptyOrWhiteSpace(value)) then
											return false, L["Please type a valid name."]
										else 
											for i,v in pairs(self.db.char.spellList) do
												if (v.name == value and i ~= passValue) then
													return false, L["Please type a unique name."]
												end
											end
											return true
										end
									end,
									usage = L["<Spell Name (Modifiers)>"],
									disabled = "~IsSpellEnabled",
									order = 3,
								},
								{
									type = "string",
									desc = L["Actual spell name description"],
									name = L["Actual spell name"],
									get = function(i) return self.db.char.spellList[i].spellName end,
									set = function (i,v) self.db.char.spellList[i].spellName = v end,
									validate = function (passValue, value)
										if (value ~= "" and self.db.profile.validateSpells 
												and self.spellInfo[value] == nil) then
											return false, L["Please type a correct spell."]
										else
											return true
										end
									end,
									usage = L["Actual spell name usage"],
									disabled = "~IsSpellEnabled",
									order = 4,
								},
								{
									type = "string",
									desc = L["Icon texture description"],
									name = L["Icon"],
									get = function(i) return self.db.char.spellList[i].texture end,
									set = function (i,v)
										if (v ~= "") then
											local _,link = GetItemInfo(v)
											if (link ~= nil) then
												v = link
											end
										end
										self.db.char.spellList[i].texture = v
									end,
									usage = L["Icon texture usage"],
									disabled = "~IsSpellEnabled",
									order = 4.5,
								},
								{
									type = "number",
									desc = L["Priority description"],
									name = L["Priority"],
									get = function(i) return self.db.char.spellList[i].priority end,
									set = function(i,v) self.db.char.spellList[i].priority = v end,
									min = 0,
									max = 100,
									step = 1,
									bigStep = 5,
									stepBasis = 5,
									disabled = "~IsSpellEnabled",
									order = 5,
								},
								{
									type = "boolean",
									name = L["Specify cast time"],
									desc = L["Specify cast time description"],
									get = function(i) return self.db.char.spellList[i].ownCastTime end,
									set = function(i,v) self.db.char.spellList[i].ownCastTime = v end,
									disabled = "~IsSpellEnabled",
									order = 6,
								},
								{
									type = "number",
									name = L["Specify cast time"],
									desc = L["Specify cast time description"],
									get = function(i) return self.db.char.spellList[i].castTime end,
									set = function(i,v) self.db.char.spellList[i].castTime = v end,
									min = 0.0,
									max = 15.0,
									step = 0.1,
									bigStep = 0.5,
									stepBasis = 0.5,
									disabled = function(i) 
										return (not self:IsSpellEnabled(i)) or 
										(self.db.char.spellList[i].ownCastTime == false) end,
									order = 7,
								},
								{
									type = 'choice',
									name = L["Target type"],
									desc = L["Target type description"],
									get = function(i) return self.db.char.spellList[i].targetType end,
									set = function(i,v) self.db.char.spellList[i].targetType = v end,
									choices = {L["Enemy"], L["Ally"], L["Disabled"]},
									disabled = "~IsSpellEnabled",
									order = 3,
								},
								{
									type = 'choice',
									name = L["Target source"],
									desc = L["Target source description"],
									get = function(i) return self.db.char.spellList[i].targetSource end,
									set = function(i,v) self.db.char.spellList[i].targetSource = v end,
									choices = self.unitIDList,
									disabled = function (i) 
											return (not self:IsSpellEnabled(i)) or 
											(self.db.char.spellList[i].targetType == L["Disabled"]) end,
									order = 3.5,
								},
								{
									type = 'choice',
									name = "Target alive",
									desc = L["Target alive description"],
									get = function(i) return self.db.char.spellList[i].targetAlive end,
									set = function(i,v) self.db.char.spellList[i].targetAlive = v end,
									choices = {L["Alive"], L["Dead"], L["Disabled"]},
									disabled = function (i) 
											return (not self:IsSpellEnabled(i)) or 
											(self.db.char.spellList[i].targetType == L["Disabled"]) end,
									order = 3.75,
								},
								{
									type = 'boolean',
									name = L["Ignore cooldown"],
									desc = L["Ignore cooldown description"],
									get = function(i) return self.db.char.spellList[i].ignoreCooldown end,
									set = function(i,v) self.db.char.spellList[i].ignoreCooldown = v end,
									disabled = "~IsSpellEnabled",
									order = 7.5,
								},
								{
									type = "execute",
									name = L["Add aura check"],
									desc = L["Add aura check description"],
									func = "AddAuraCheck",
									buttonText = L["Add"],
									disabled = "~IsSpellEnabled",
									order = 8,
								},
								{
									type = "execute",
									name = L["Add status check"],
									desc = L["Add status check description"],
									func = "AddStatusCheck",
									buttonText = L["Add"],
									disabled = "~IsSpellEnabled",
									order = 9,
								},
						
								{
									type = "execute",
									name = L["Add item ready check"],
									desc = L["Add item ready check description"],
									func = "AddItemReadyCheck",
									buttonText = L["Add"],
									disabled = "~IsSpellEnabled",
									order = 12,
								},
								{
									type = "execute",
									name = L["Add equipment ready check"],
									desc = L["Add equipment ready check description"],
									func = "AddEquipmentReadyCheck",
									buttonText = L["Add"],
									disabled = "~IsSpellEnabled",
									order = 13,
								},
								{
									type = "execute",
									name = L["Add spell ready check"],
									desc = L["Add spell ready check description"],
									func = "AddSpellReadyCheck",
									buttonText = L["Add"],
									disabled = "~IsSpellEnabled",
									order = 14,
								},
							}

							 -- end storage of curSpellOutput
							 
							for pos,check in pairs(v.conditions) do
								--Check the type
								if check.type == "aura" then
									tinsert(curSpellOutput, self:curSpellOutputAura(i,v, check, pos))
								elseif check.type == "status" then
									tinsert(curSpellOutput, self:curSpellOutputStatus(i,v, check, pos))
								elseif check.type == "itemReady" then
									tinsert(curSpellOutput, self:curSpellOutputItemReady(i,v, check, pos))
								elseif check.type == "equipmentReady" then
									tinsert(curSpellOutput, self:curSpellOutputEquipmentReady(i,v, check, pos))
								elseif check.type == "spellReady" then
									tinsert(curSpellOutput, self:curSpellOutputSpellReady(i,v,check,pos))
								else 
									self:Print("Invalid check type " .. check.type)
									self:Print("check type must be one of: aura, status, itemReady, equipmentReady or spellReady")
								end
							end
							
							return curSpellOutput
							
						end,
					}
					tinsert(output, curSpell)
				end
				
				return output
				
			end,
		},
	},
}

-- Helper function for self.options args
-- Arguments
-- i is the index of the spell
-- v is the spell
-- check is the condition
-- pos is the position of the condition
function self:curSpellOutputAura(i,v, check, pos)
 assert(check.type == "aura")
 return {
		type = "group",
		name = check.name,
		desc = L["Configure aura check description"],
		child_passValue = i,
		child_passValue2 = pos,
		disabled = "~IsSpellEnabled",
		args = {
			{
				--A bit of a hack but what the heck...
				type = "string",
				name = L["Check type"],
				desc = L["Configure condition description"],
				get = function () return "Aura" end,
				set = function() end,
				usage = L["Check type usage"],
				order = 1,
			},
			{
				type = "execute",
				name = L["Delete check"],
				desc = L["Delete check description"],
				func = "DeleteCheck",
				confirmText = L["Confirm"] .. L["Delete"],
				buttonText = L["Delete"] .. L["Check"],
				order = 2,
			},
			{
				type = 'choice',
				name = L["Unit source"],
				desc = L["Unit source description"],
				get = function(i, pos) 
					return self.db.char.spellList[i].conditions[pos].unitType end,
				set = function(i, pos, v) 
					self.db.char.spellList[i].conditions[pos].unitType = v end,
				choices = self.unitIDList,
				order = 2.5,
			},
			{
				type = "string",
				name = L["Aura name"],
				desc = L["Aura name description"],			
				get = function(i, pos) return self.db.char.spellList[i].conditions[pos].name end,
				set = function(i, pos, value) self.db.char.spellList[i].conditions[pos].name = value end,
				usage = L["Aura name usage"],
				order = 3,
			},
			{
				type = "number",
				name = L["Minimum stack"],
				desc = L["Minimum stack description"],
				get = function(i, pos) return self.db.char.spellList[i].conditions[pos].minStack end,
				set = function(i, pos, value)
					self.db.char.spellList[i].conditions[pos].minStack = value							
					if (value > self.db.char.spellList[i].conditions[pos].maxStack) then
						self.db.char.spellList[i].conditions[pos].maxStack = value
					end
				end,
				min = 0,
				max = 100,
				step = 1,
				disabled = function(i, pos) return self.db.char.spellList[i].conditions[pos].castWhenExists == false end,
				order = 5,
			},
			{
				type = "number",
				name = L["Maximum stack"],
				desc = L["Maximum stack description"],
				get = function(i, pos) return self.db.char.spellList[i].conditions[pos].maxStack end,
				set = function(i, pos, value)
					self.db.char.spellList[i].conditions[pos].maxStack = value
					if (value < self.db.char.spellList[i].conditions[pos].minStack) then
						self.db.char.spellList[i].conditions[pos].minStack = value
					end
				end,
				min = 0,
				max = 100,
				step = 1,
				disabled = function(i, pos) 
					return self.db.char.spellList[i].conditions[pos].castWhenExists == false end,
				order = 6,
			},
			{
				type = "boolean",
				name = L["Only player casted"],
				desc = L["Only player casted description"],
				get = function(i, pos) 
					return self.db.char.spellList[i].conditions[pos].playerCastOnly end,
				set = function(i, pos, value) 
					self.db.char.spellList[i].conditions[pos].playerCastOnly = value end,
				order = 7,
			},
			{
				type = "choice",
				name = L["Spell affects aura"],
				desc = L["Spell affects aura description"],
				get = function(i, pos) 
					return self.db.char.spellList[i].conditions[pos].spellTriggers end,
				set = function(i, pos, value) 
					self.db.char.spellList[i].conditions[pos].spellTriggers = value end,
				choices = {L["Add"], L["Subtract"], L["Disabled"]},
				order = 8,
			},
			{
				type = "boolean",
				name = L["Refresh aura"],
				desc = L["Refresh aura description"],
				get = function(i, pos) 
					return self.db.char.spellList[i].conditions[pos].refreshAura end,
				set = function(i, pos, value) 
					self.db.char.spellList[i].conditions[pos].refreshAura = value end,
				order = 9,
			},
			{
				type = "number",
				name = L["Refresh leeway"],
				desc = L["Refresh leeway description"],
				get = function(i, pos) 
					return self.db.char.spellList[i].conditions[pos].refreshDuration end,
				set = function(i, pos, value) 
					self.db.char.spellList[i].conditions[pos].refreshDuration = value end,
				min = 0,
				max = 60,
				bigStep = 1,
				step = 0.1,
				stepBasis = 1,
				order = 10,
			},
		}
	}
end 
-- end aura

-- Helper function for self.options args
-- Arguments
-- i is the index of the spell
-- v is the spell
-- check is the condition
-- pos is the position of the condition
function self:curSpellOutputStatus(i,v, check, pos)
 assert(check.type == "status")
 return {
		type = "group",
		name = check.name,
		desc = L["Configure status check description"],
		child_passValue = i,
		child_passValue2 = pos,
		disabled = "~IsSpellEnabled",
		args = 
		 {
			{
				--A bit of a hack but what the heck...
				type = "string",
				name = L["Check type"],
				desc = L["Check type description"],
				get = function () return L["Status"] end,
				set = function() end,
				usage = L["Check type usage"],
				order = 1,
			},
			{
				type = "execute",
				name = L["Delete check"],
				desc = L["Delete check description"],
				func = "DeleteCheck",
				confirmText = L["Confirm"]..L["Delete"],
				buttonText = L["Delete"]..L["Check"],
				order = 2,
			},
			{
				type = 'choice',
				name = L["Unit source"],
				desc = L["Unit source description"],
				get = function(i, pos) 
					return self.db.char.spellList[i].conditions[pos].unitType end,
				set = function(i, pos, v)
					self.db.char.spellList[i].conditions[pos].unitType = v
					self:UpdateStatus(i, pos)
				end,
				choices = self.unitIDList,
				order = 2.5,
			},
			{
				type = "choice",
				name = L["Health"],
				desc = L["Health condition description"],
				get = function (i, pos) return self.db.char.spellList[i].conditions[pos].health end,
				set = function (i, pos, value)
					self.db.char.spellList[i].conditions[pos].health = value
					self:UpdateStatus(i, pos)
				end,
				choices = {L["Disabled"], L["More than"], L["Less than"]},
				order = 3,
			},
			{
				type = "number",
				name = L["Health amount"],
				desc = L["Health condition description"],
				get = function (i, pos) 
						return self.db.char.spellList[i].conditions[pos].healthAmount end,
				set = function (i, pos, value)
					self.db.char.spellList[i].conditions[pos].healthAmount = value
					self:UpdateStatus(i, pos)
				end,
				step = 0.01,
				bigStep = 0.05,
				stepBasis = 0.05,
				isPercent = true,
				disabled = function() return self.db.char.spellList[i].conditions[pos].health == L["Disabled"] end,
				order = 4,
			},
			{
				type = "choice",
				name = L["Power"],
				desc = L["Power condition description"],
				get = function (i, pos) 
						return self.db.char.spellList[i].conditions[pos].power end,
				set = function (i, pos, value)
					self.db.char.spellList[i].conditions[pos].power = value
					self:UpdateStatus(i, pos)
				end,
				choices = {L["Disabled"], L["More than"], L["Less than"]},
				order = 5,
			},
			{
				type = "number",
				name = L["Power amount"],
				desc = L["Power amount description"],
				get = function (i, pos) 
						return self.db.char.spellList[i].conditions[pos].powerAmount end,
				set = function (i, pos, value)
					self.db.char.spellList[i].conditions[pos].powerAmount = value
					self:UpdateStatus(i, pos)
				end,
				step = 0.01,
				bigStep = 0.05,
				stepBasis = 0.05,
				isPercent = true,
				disabled = function() 
							return self.db.char.spellList[i].conditions[pos].power 
							== L["Disabled"] end,
				order = 6,
			},
			{
				type = "choice",
				name = L["Combo points"],
				desc = L["Combo points description"],
				get = function (i, pos) 
					return self.db.char.spellList[i].conditions[pos].combo end,
				set = function (i, pos, value)
					self.db.char.spellList[i].conditions[pos].combo = value
					self:UpdateStatus(i, pos)
				end,
				choices = {L["Disabled"], L["More than"], L["Less than"]},
				order = 7,
			},
			{
				type = "number",
				name = L["Combo point amount"],
				desc = L["Combo point amount description"],
				get = function (i, pos) 
						return self.db.char.spellList[i].conditions[pos].comboPoints end,
				set = function (i, pos, value)
					self.db.char.spellList[i].conditions[pos].comboPoints = value
					self:UpdateStatus(i, pos)
				end,
				min  = 0,
				max  = 5,
				step = 1,
				bigStep = 1,
				stepBasis = 1,
				isPercent = false,
				disabled = function() 
							return self.db.char.spellList[i].conditions[pos].combo 
							== L["Disabled"] end,
				order = 8,
			},
			  -- add threat check
			
		}
	}
end -- end status check

-- Helper function for self.options args
-- Arguments
-- i is the index of the spell
-- v is the spell
-- check is the condition
-- pos is the position of the condition
function self:curSpellOutputItemReady(i,v, check, pos)
 assert(check.type == "itemReady")
 return {
			type = "group",
			name = check.name,
			desc = L["Configure item ready check description"],
			child_passValue = i,
			child_passValue2 = pos,
			disabled = "~IsSpellEnabled",
			args = 
			{
				{
					type = "string",
					name = L["Check type"],
					desc = L["Check type description"],
					get = function () return L["Item ready"] end,
					set = function() end,
					usage = L["Check type usage"],
					order = 1,
				},
				{
					type = "execute",
					name = L["Delete check"],
					desc = L["Delete check description"],
					func = "DeleteCheck",
					confirmText = L["Confirm"]..L["Delete"],
					buttonText = L["Delete"]..L["Check"],
					order = 2,
				},
				{
					type = "string",
					name = L["Item name"],
					desc = L["Item name description"],
					get = function(i, pos) 
							return self.db.char.spellList[i].conditions[pos].name end,
					set = function(i, pos, value) 
							self.db.char.spellList[i].conditions[pos].name = value end,
					usage = L["Item name usage"],
					order = 3,
				},
				{
					type = "boolean",
					name = L["Cast if ready"],
					desc = L["Cast if ready description"],
					get = function(i, pos) return self.db.char.spellList[i].conditions[pos].ready end,
					set = function(i, pos, value) self.db.char.spellList[i].conditions[pos].ready = value end,
					order = 4,
				},

			}  -- end of args
		}
end


-- Helper function for self.options args
-- Arguments
-- i is the index of the spell
-- v is the spell
-- check is the condition
-- pos is the position of the condition
function self:curSpellOutputEquipmentReady(i,v, check, pos)
 assert(check.type == "equipmentReady")
 return {
			type = "group",
			name = check.name,
			desc = L["Equipment ready check description"],
			child_passValue = i,
			child_passValue2 = pos,
			disabled = "~IsSpellEnabled",
			args = 
			{
				{
					type = "string",
					name = L["Check type"],
					desc = L["Check type description"],
					get = function () return L["Equipment Ready"] end,
					set = function() end,
					usage = L["Check type usage"],
					order = 1,
				},
				{
					type = "execute",
					name = L["Delete check"],
					desc = L["Delete check description"],
					func = "DeleteCheck",
					confirmText = L["Confirm"].. L["Delete"],
					buttonText = L["Delete"]..L["Check"],
					order = 2,
				},
				{
					type = "string",
					name = L["Equipment name"],
					desc = L["Equipment name description"],
					get = function(i, pos) 
							return self.db.char.spellList[i].conditions[pos].name end,
					set = function(i, pos, value) 
							self.db.char.spellList[i].conditions[pos].name = value end,
					usage = L["Equipment name usage"],
					order = 3,
				},
				{
					type = "boolean",
					name = L["Cast if ready"],
					desc = L["Cast if ready description"],
					get = function(i, pos) 
							return self.db.char.spellList[i].conditions[pos].ready end,
					set = function(i, pos, value) 
							self.db.char.spellList[i].conditions[pos].ready = value end,
					order = 4,
				},
				{
					type = "execute",
					name = L["List equipment"],
					desc = L["List equipment description"],
					func = "ListEquipmentSlots",
					buttonText = L["List"],
					order = 6,
				}
			}  -- end of args list
		}
end

-- Helper function for self.options args
-- Arguments
-- i is the index of the spell
-- v is the spell
-- check is the condition
-- pos is the position of the condition
function self:curSpellOutputSpellReady(i,v, check, pos)
 assert(check.type == "spellReady")
 return {
			type = "group",
			name = check.name,
			desc = L["Spell ready check description"],
			child_passValue = i,
			child_passValue2 = pos,
			disabled = "~IsSpellEnabled",
			args = 
			 {
				{
					type = "string",
					name = L["Check type"],
					desc = L["Check type description"],
					get = function () return L["Spell Ready"] end,
					set = function() end,
					usage = L["Check type usage"],
					order = 1,
				},
				{
					type = "execute",
					name = L["Delete check"],
					desc = L["Delete check description"],
					func = "DeleteCheck",
					confirmText = L["Confirm"].. L["Delete"],
					buttonText = L["Delete"]..L["Check"],
					order = 2,
				},
				{
					type = "string",
					name = L["Spell name"],
					desc = L["Spell name description"],
					get = function(i, pos) 
							return self.db.char.spellList[i].conditions[pos].name end,
					set = function(i, pos, value) 
							self.db.char.spellList[i].conditions[pos].name = value end,
					validate = function (passValue, value)
						if (value ~= "" and self.db.profile.validateSpells and self.spellInfo[value] == nil) then
							return false, L["Please type a valid spell."]
						else
							return true
						end
					end,
					usage = "<Spell Name>",
					order = 3,
				},
				{
					type = "boolean",
					name = L["Cast if ready"],
					desc = L["Cast if ready description"],
					get = function(i, pos)
					 return self.db.char.spellList[i].conditions[pos].ready end,
					set = function(i, pos, value) 
					  self.db.char.spellList[i].conditions[pos].ready = value end,
					order = 4,
				},

			}    -- end args
	    }
end				
									
self.spellInfo = {}

self.Tooltip = nil

self:SetDatabase("TrainingWheelsDB")
self:SetDefaultProfile("Default")
self:SetDatabaseDefaults("profile", {
	spellFrameX = GetScreenWidth() / 2,
	spellFrameY = GetScreenHeight() / 2,
	leeway = 0.1,
	spellLists = {},
	validateSpells = true,
	showLabels = true,
	maxSpells = 5,
	latency = 0.1,
	alpha   = 1
	-- scale size
	-- defaults
})
self:SetDatabaseDefaults("char", {
	spellList = {},
})

-- Runs on startup
function self:OnInitialize()
	self:InitSpellFrame()
	self.Tooltip = CreateFrame("GameTooltip", "TrainingWheels_GameTooltip", nil, "GameTooltipTemplate")
	self:SetConfigTable(self.options)
	self:SetConfigSlashCommand(L["/TrainingWheels"], L["/TW"])
	local migrateResults, migrateMsg = self:MigrateSpellList(self.db.char.spellList)
	if (not migrateResults) then
		message(migrateMsg)
		self.db.char.spellList = {}
	end
	self:SaveSnapshot("Login")
	self:FetchSpellInfo()
	self:FetchEquipmentList()
	self:FetchCombatRating()
	
	self:Monitor()
	self.SpellFrame:Show()
	
end

function self:OnEnable()
	if self.DEBUG then self:Print("calling OnEnable") end
	
	self:Monitor()
	self.SpellFrame:Show()
	
	if self.DEBUG then self:Print("exiting OnEnable") end
end

function self:OnDisable()
	self.SpellFrame:Hide()
end

function self:Default(tableName, key, defaultValue)
	if (tableName[key] == nil) then
		tableName[key] = defaultValue
	end
	return tableName[key]
end

function self:Monitor()

	if self.DEBUG then self:Print("Monitor() at : " ..  GetTime()) end
	
	self:AddRepeatingTimer(0, "CheckRefresh")
	--self:AddEventListener("UNIT_SPELLCAST_STOP", "ProcessCast")
	self:AddEventListener("Blizzard","UNIT_SPELLCAST_SUCCEEDED", "ProcessCast")
	self:AddEventListener("Blizzard","SPELLS_CHANGED", "FetchSpellInfo")
	self:AddEventListener("Blizzard","TIME_PLAYED_MSG", "FetchSpellInfo")
	--self:AddEventListener("UNIT_SPELLCAST_CHANNEL_STOP", "ProcessCast")
	self:AddEventListener("Blizzard","UNIT_AURA", "ProcessAura")
	self:AddEventListener("Blizzard","PLAYER_TARGET_CHANGED", "ScheduleRefreshSpellFrame")
	self:AddEventListener("Blizzard","UNIT_INVENTORY_CHANGED", "PreFetchEquipmentList")
	self:AddEventListener("Blizzard","COMBAT_RATING_UPDATE", "PreFetchCombatRating")
	self:AddSecureHook("CastSpellByName", "ScheduleRefreshSpellFrame")
	self:AddSecureHook("CastSpell", "ScheduleRefreshSpellFrame")
	self:AddSecureHook("CastShapeshiftForm", "ScheduleRefreshSpellFrame")
	self:AddSecureHook("SpellStopCasting", "ScheduleRefreshSpellFrame")
	
	if self.DEBUG then self:Print("monitoring end of function at : " ..  GetTime()) end
	
end

function self:CheckRefresh()


	self.thisFrameRefreshed = false
	self.time = GetTime()
	
	if (self.nextFrameRefresh) then
		self.nextFrameRefresh = false
		self:RefreshSpellFrame()
		
	elseif (self.time - self.lastRefresh >= 0.1) then
		self:RefreshSpellFrame()	
	end
	
end

function self:PreFetchCombatRating(arg1)
	if (arg1 == "player") then
	    self:FetchCombatRating()
	end
end	

function self:FetchCombatRating()
	self.haste =  GetCombatRatingBonus(20) *.01 
	
	local BASE_GCD = 1.5  -- modify check to change to 1 for rogues and kitties
	 -- TODO if class if rogue or kitty, BASE_GCD = 1

	local MIN_GCD  = 1    -- minimum GCD
	self.myGCD   =   math.max(MIN_GCD, BASE_GCD  / ( 1 + self.haste))
end

function self:PreFetchEquipmentList(arg1)
	if (arg1 == "player") then
		self:FetchEquipmentList()
	end
end

function self:FetchEquipmentList()
	local MAX_SLOTS = 19 -- Maximum number of slots for player
	
	for i=0,MAX_SLOTS do
		local result = GetInventoryItemLink("player", i)
		if (result ~= nil) then
			self.playerEquipment[i] = GetItemInfo(result)
		else
			self.playerEquipment[i] = nil
		end
	end
end

function self:ProcessCast(arg1)
	if (arg1 == "player" or arg1 == "pet") then
		self:ScheduleRefreshSpellFrame()
	end
end

-- if arg1 is a valid unitID then run self:ScheduleRefreshSpellFrame()
-- otherwise do nothing
function self:ProcessAura(arg1)
	if (arg1 == "target" or arg1 == "player" or arg1 == "pet" or arg1 == "mouseover" or arg1 == "focus") then
		self:ScheduleRefreshSpellFrame()
	end
end

function self:FetchSpellInfo()
	local i = 1
	local book = "spell"
	while true do
		local spellName, spellRank = GetSpellName(i, book)
		if not spellName then
			if (book == "spell") then
				i = 1
				book = "pet"
			else
				break
			end
		else
			
			local texture = GetSpellTexture(i, book)
		   
			--Does it already exist?
			local skip = false
			if self.spellInfo[spellName] ~= nil then
				if strlen(self.spellInfo[spellName].rank) >= strlen(spellRank) then
					skip = true
				elseif (strlen(self.spellInfo[spellName].rank) == strlen(spellRank) and self.spellInfo[spellName].rank > spellRank) then
					skip = true
				end
			end
			if not skip then
				--Get the info from tooltip
				--self.Tooltip:ClearLines()
				--self.Tooltip:SetSpell(i, "spell")
				self.Tooltip:SetOwner(UIParent, "ANCHOR_NONE")
				self.Tooltip:SetSpell(i, "spell")
				local castTime = nil
				local cooldown = 0
				for i=1,self.Tooltip:NumLines() do

					local mytext = getglobal("TrainingWheels_GameTooltipTextLeft"..i)
					if mytext ~= nil then
						local text = mytext:GetText()
						if (text ~= nil) then
							local _,_,ct,_ = strfind(text, "^"..self.globalStrings["SPELL_CAST_TIME_SEC"])
							if (ct ~= nil) then
								castTime = tonumber(ct)
								if (castTime ~= nil) then break end
							else
								_,_,ct,_ = strfind(text, "^"..self.globalStrings["SPELL_CAST_TIME_MIN"])
								if (ct ~= nil) then
									castTime = tonumber(ct) * 60
									if (castTime ~= nil) then break end
								end
							end
						end
					else
						break
					end
				end
				local righttext= getglobal("TrainingWheels_GameTooltipTextRight3")
				if righttext ~= nil then
					local text = righttext:GetText()
					if (text ~= nil) then
						local _,_,cd,_ = strfind(text, "^"..self.globalStrings["SPELL_RECAST_TIME_SEC"])
						if (cd ~= nil) then
							cooldown = tonumber(cd)
						else
							_,_,cd,_ = strfind(text, "^"..self.globalStrings["SPELL_RECAST_TIME_MIN"])
							if (cd ~= nil) then
								cooldown = tonumber(cd) * 60
							end
						end
					end
				end
				self.Tooltip:Hide()
				
				local isPetSpell = (book ~= "spell")

				if (castTime == nil) then castTime = 0.0 end
				self.spellInfo[spellName] = {id=i, spellName=spellname, cooldown=cooldown, rank=spellRank, texture=texture, castTime=castTime, isPetSpell = isPetSpell}
			end
			i = i + 1
		end
	end
end

function self:HasSpellLists(noincluded)
	if (#self.db.profile.spellLists > 0) then
		return true
	elseif (noincluded ~= true and #self.includedLists[self.className] > 0) then
		return true
	end
	return false
end

function self:GetSpellLists(noincluded)
	local output = newList("")
	for i,v in pairs(self.db.profile.spellLists) do
		tinsert(output, v.name)
	end
	if (noincluded ~= true and self.includedLists[self.className] ~= nil) then
		for i,v in pairs(self.includedLists[self.className]) do
			--Check if exists in Output already
			local found = false
			for j,v2 in pairs(output) do
				if (v2 == v.name) then
					found = true
					break
				end
			end
			if not found then
				tinsert(output, v.name)
			end
		end
	end
	return "@list", unpackListAndDel(output)
end

function self:SaveSpellList(name)
	local spellList = {
		name= name,
		list= self:CloneTable(self.db.char.spellList),
	}
	tinsert(self.db.profile.spellLists, spellList)

	if (self.includedLists[self.className] ~= nil) then
		for i,v in pairs(self.includedLists[self.className]) do
			if v.name == name then
				self:Print("Overridden included spell list \"".. name.."\". It is now inaccessible until you delete your saved spell list with the same name.")
			end
		end
	end
	
	self:SaveSnapshot("Saved as "..name)
	
	Rock("LibRockConfig-1.0"):RefreshConfigMenu(TrainingWheels)
end

function self:SaveSnapshot(name)
	tinsert(self.snapshots, {name=name, list=self:CloneTable(self.db.char.spellList)})
end

function self:LoadSnapshot(index, confirm)
	if (confirm == nil) then
		local dialog = StaticPopup_Show("TrainingWheels_ConfirmLoadSnapshot")
		if (dialog) then
			dialog.data = index
		end
	else
		if (tonumber(index) <= #self.snapshots) then
			self.db.char.spellList = self:CloneTable(self.snapshots[tonumber(index)].list)
			self:RefreshSpellList()
		end
	end
end

function self:GetSnapshots()
	local output = {}
	local digits = strlen(tostring(#self.snapshots))
	for i,v in pairs (self.snapshots) do
		output[i] = strrep("0", digits - strlen(tostring(i)))..i .. " - "..v.name
	end
	return output
end

function self:LoadSpellList(name, nosave)
	local spellList = nil
	for i,v in pairs(self.db.profile.spellLists) do
		if (v.name == name) then
			spellList = v
			break
		end
	end
	if (spellList == nil and self.includedLists[self.className] ~= nil) then
		for i,v in pairs(self.includedLists[self.className]) do
			if (v.name == name) then
				spellList = v
				break
			end
		end
	end
	if (nosave ~= true) then
		self:SaveSnapshot("Before loading ".. name)
	end
	if (spellList ~= nil) then
		local migrateResults, migrateMsg = self:MigrateSpellList(spellList.list)
		if not migrateResults then
			message(migrateMsg)
			self:LoadSnapshot(#self.snapshots, true)
			return false
		else
			for j,spell in pairs(spellList.list) do
				for k, curSpell in pairs(self.db.char.spellList) do
					if (spell.name == curSpell.name) then
						tremove(self.db.char.spellList, k)
						break
					end
				end
				local result, msg = self:CloneTable(spell)
				if (result == false) then
					--Load back the last spell list if error
					--We don't want our users to end up with corrupted data no matter what
					self:LoadSnapshot(#self.snapshots, true)
					self:Print("Error loading Spell List: "..msg)
					return false, "Error loading Spell List: "..msg
				end
				tinsert(self.db.char.spellList, result)
			end
			self:RefreshSpellFrame()
			Rock("LibRockConfig-1.0"):RefreshConfigMenu(TrainingWheels)
			--No errors, save another snapshot
			if (nosave ~= true) then
				self:SaveSnapshot("After loading ".. name)
			end
			return true
		end
	end
	return false, "Unable to find Spell List ".. name
end

function self:ClearSpellList()
	self:SaveSnapshot("Before clear")
	self.db.char.spellList = {}
	self:SaveSnapshot("After clear")
	self:RefreshSpellFrame()
	Rock("LibRockConfig-1.0"):RefreshConfigMenu(TrainingWheels)
end

function self:DeleteSpellList(name, confirm)
	if (confirm == nil) then
		local dialog = StaticPopup_Show("TrainingWheels_ConfirmDeleteSpellList")
		if (dialog) then
			dialog.data = name
		end
	else
		for i,v in pairs(self.db.profile.spellLists) do
			if (v.name == name) then
				tremove(self.db.profile.spellLists, tonumber(name))
				Rock("LibRockConfig-1.0"):RefreshConfigMenu(TrainingWheels)
				break
			end
		end
	end
end

function self:RefreshSpellList()

   if self.DEBUG then self:Print("In RefreshSpellList")     end
   
	sort(self.db.char.spellList, function (a,b)
		return (a.priority<b.priority or (a.priority == b.priority and a.name < b.name))
	end)
	Rock("LibRockConfig-1.0"):RefreshConfigMenu(TrainingWheels)
	
	self:RefreshSpellFrame()
end

-- Initialize a Spell and set to defaults
function self:AddSpell(name)
	local spell = {
		version = self.spellVersion,
		name = name,
		spellName = name,
		iconPath = "",
		priority = 50,
		enabled = true,
		ownCastTime = false,
		targetType = "Enemy",
		targetSource = "Target",
		targetAlive = "Alive",
		ignoreCooldown = false,
		castTime = 0.0,
		conditions = {}
	}
	tinsert(self.db.char.spellList, spell)
	self:RefreshSpellList()
end

function self:DeleteSpell(i, confirm)
	tremove(self.db.char.spellList, i)
	self:RefreshSpellList()
end

function self:IsSpellEnabled(i)
	return (self.db.char.spellList[i].enabled == true)
end

-- Initialize an AuraCheck and set to defaults
function self:AddAuraCheck(i)
	local aura = {
		type = "aura",
		name = "New Aura",
		unitType = "Target",
		minStack = 0,
		maxStack = 0,
		refreshAura = true,
		refreshDuration = 0,
		playerCastOnly = true,
		spellTriggers = "Add",
	}
	tinsert(self.db.char.spellList[i].conditions, aura)
	self:RefreshSpellList()
end

-- Initialize a StatusCheck and set to defaults
function self:AddStatusCheck(i)
	local status = {
		type = "status",
		name = "New Status",
		unitType = "Player",
		health = "Disabled",        
		healthAmount = 0.75,
		power = "Disabled",
		powerAmount = 0.75,
		combo = "Disabled",
		comboPoints = 0,
	}
	tinsert(self.db.char.spellList[i].conditions, status)
	self:RefreshSpellList()
end

-- Initialize an Item Ready Check and set to defaults
function self:AddItemReadyCheck(i)
	local itemReady = {
		type = "itemReady",
		name = "New Item Ready",
		ready = true,
		spellTriggers = true,
	}
	tinsert(self.db.char.spellList[i].conditions, itemReady)
end

-- Initialize an Equipment Ready Check and set to defaults
function self:AddEquipmentReadyCheck(i)
	local equipmentReady = {
		type = "equipmentReady",
		name = "New Equipment Ready",
		ready = true,
		spellTriggers = true,
	}
	tinsert(self.db.char.spellList[i].conditions, equipmentReady)
end

-- Initialize a Spell Ready Check and set to defaults
function self:AddSpellReadyCheck(i)
	local spellReady = {
		type = "spellReady",
		name = "New Spell Ready",
		ready = true,
		spellTriggers = true,
	}
	tinsert(self.db.char.spellList[i].conditions, spellReady)
end

-- Deletes a check
function self:DeleteCheck(i, j)
	tremove(self.db.char.spellList[i].conditions, j)
	self:RefreshSpellList()
end

-- UpdateString 
-- Arguments
--          beginStr : non null string
--			cmp : string, "More than", "Less than", other possibly nil
--          amount : integer
--          endStr   : non null string
-- Returns
-- 			"str >= amountendStr" if cmp == "More than"
--          "str < amountendStr " if cmp == "Less than"
--           empty string otherwise
function self.UpdateStr(beginStr, cmp, amount, endStr) 
	returnStr = ""	
    if (cmp == "More than") then
		returnStr = beginStr .." >= ".. amount .. endStr
	elseif (cmp == "Less than") then
		returnStr = beginStr .. " < " ..amount .. endStr
	end
	
	return returnStr
end

-- UpdateStatus
-- updates self.db.char.spellList[i].conditions[pos].name
-- with health, power, combo string if needed, other specifies Unspecified
function self:UpdateStatus(i, pos)


	local mySpellListConditions =   self.db.char.spellList[i].conditions[pos]   -- replace occurences below with this
	local healthStr = self.UpdateStr("H", 
								mySpellListConditions.health ,
								mySpellListConditions.healthAmount *100,
								"%")
	
	local powerStr =  self.UpdateStr("P", 
								mySpellListConditions.power,
								mySpellListConditions.powerAmount *100,
								"%")
	local comboStr = self.UpdateStr("CP", 
								mySpellListConditions.combo,
								mySpellListConditions.comboPoints,
								"")														
	
	local name = strtrim(healthStr.." "..powerStr .. " " .. comboStr)
	if (name ~= "") then
		mySpellListConditions.name = mySpellListConditions.unitType.." ("..name..")"
	else
		mySpellListConditions.name = "Unspecified "..mySpellListConditions.unitType.." Status"
	end
	self:RefreshSpellList()
end


function self:RefreshSpellFrame()
    if self.DEBUG then  self:Print("Entering RefreshSpellFrame at " .. GetTime()) end
    
	if (self.thisFrameRefreshed) then
		return
	end
	self.thisFrameRefreshed = true
	
	self.lastRefresh = self.time
	
	self.SpellFrame:Clear()
	self.SpellFrame:SetAlpha(self.db.profile.alpha)	 -- sets alpha
	                                                 -- set size here too
	                                                 
	--Only if player in combat!
	--if (UnitAffectingCombat("player")) then

		local playerLeeway, petLeeway = self.db.profile.leeway, self.db.profile.leeway
		local currentlyCastingPlayerSpell, _, startTime, endTime = nil
		currentlyCastingPlayerSpell, _, _, icon, startTime, endTime = UnitCastingInfo("player")
		if (currentlyCastingPlayerSpell == nil) then
			currentlyCastingPlayerSpell, _, _, icon, startTime, endTime = UnitChannelInfo("player")
		end
		--OOH we are casting a spell
		if (currentlyCastingPlayerSpell ~= nil) then
			playerLeeway = playerLeeway + (endTime - startTime) / 1000 - self.db.profile.latency
			if (self.spellInfo[currentlyCastingPlayerSpell] ~= nil) then
				self.spellInfo[currentlyCastingPlayerSpell].endCast = endTime / 1000 + self.db.profile.latency
			end
			if (playerLeeway < 0) then playerLeeway = 0 end
		end
		
		local currentlyCastingPetSpell, _, startTime, endTime = nil
		currentlyCastingPetSpell, _, _, icon, startTime, endTime = UnitCastingInfo("pet")
		if (currentlyCastingPetSpell == nil) then
			currentlyCastingPetSpell, _, _, icon, startTime, endTime = UnitChannelInfo("pet")
		end
		--OOH pet is casting a spell
		if (currentlyCastingPetSpell ~= nil) then
			petLeeway = petLeeway + (endTime - startTime) / 1000 - self.db.profile.latency
			if (self.spellInfo[currentlyCastingPetSpell] ~= nil) then
				self.spellInfo[currentlyCastingPetSpell].endCast = endTime / 1000 + self.db.profile.latency
			end
			if (petLeeway < 0) then petLeeway = 0 end
		end		
		
		local auraList = newDict()
		
		-- TODO improve this to just run this through valid unitIDs in use
--		setmetatable(auraList, { _mode="kv"})
		auraList["Player"] = {self:GetAuras("Player")}
		auraList["Target"] = {self:GetAuras("Target")}
		auraList["Focus"] = {self:GetAuras("Focus")}
		auraList["Mouseover"] = {self:GetAuras("Mouseover")}
		auraList["Pet"] = {self:GetAuras("Pet")}
		-- improve this to only check auras as needed, save result for one pass of function
		
--		for _,v in pairs(auraList) do
--			   setmetatable(v, { _mode="kv"})	
--			   setmetatable(v[1], { _mode="kv"})
--			   setmetatable(v[2], { _mode="kv"})
--		end
			   
		-- get spell haste from current gear as a decimal 
						
		local BASE_GCD = 1.5  -- modify check to change to 1 for rogues and kitties
		 -- TODO if class if rogue or kitty, BASE_GCD = 1
		 
		local MIN_GCD  = 1    -- minimum GCD
		local myGCD   =   math.max(MIN_GCD, BASE_GCD  / ( 1 + self.haste))
		--Shit job begins NOW:
		--Obtain the list of shit
		
		
		local count = 0
		
--		if (self.DEBUG) then
--			self:Print("Working in SpellRefreshFrame at time ")
--			self:Print(GetTime())
--			self:Print(self.time)
--		end
		
		for i,v in pairs(self.db.char.spellList) do
			--Check that the spell exists in our db first,
			--and that it is a spell that can be cast on our target

			--Make sure defaults are set properly...
			self:Default(v, "targetSource", "Target")
			self:Default(v, "targetAlive", "Alive")
			self:Default(v, "ignoreCooldown", false)
			if (v.enabled and (v.spellName == "" or self.spellInfo[v.spellName] ~= nil)
				 and
					(v.targetType == "Disabled"
					or (UnitExists(v.targetSource) == 1 and 
						(((v.targetType == "Enemy" and UnitIsFriend(v.targetSource, "player") ~= 1)
						or (v.targetType == "Ally" and UnitIsFriend(v.targetSource, "player") == 1))
						) and
							((v.targetAlive == "Disabled") or
							(v.targetAlive == "Alive" and UnitIsDead(v.targetSource) ~= 1) or
							(v.targetAlive == "Dead" and UnitIsDead(v.targetSource) == 1)))))
			then
				count = count + 1
				if (self.refreshList[count] == nil) then
					self.refreshList[count] = {}
				end
		
				--It's clear.. let's get the cast time
				local castTime = v.castTime
				if ( (not v.ownCastTime) and v.spellName ~= "" and self.spellInfo[v.spellname]) then
					castTime = self.spellInfo[v.spellName].castTime
				end
				
				--Let's get if the spell is a pet spell or not (GEEZ)
				local isPetSpell = false
				if (v.spellName ~= "" and self.spellInfo[v.spellname]) then
					isPetSpell = self.spellInfo[v.spellName].isPetSpell
				end
				
				local leeway = 0
				
				if (isPetSpell) then
					currentlyCastingSpell = currentlyCastingPetSpell
					leeway = petLeeway
				else
					currentlyCastingSpell = currentlyCastingPlayerSpell
					leeway = playerLeeway
				end
				
				local nextCast = 9999
				local earlyCast = false
				--Cast time complete
				
				--Lets move on to the conditions and see if they are true
				-- make a subfunction for each of the checkTypes:
				-- aura, status, spellReady, itemReady, equipmentReady
				-- break out of loop as soon as any check fails
				-- maybe reorder based on failure, to improve performance
				local pass = true;
				for j,check in pairs(v.conditions) do       -- i think we can throw out j
					if (check.type == "aura") then
						--It is inevitable
						local aura = {}
						setmetatable(aura, { _mode="kv"})
						if (check.playerCastOnly) then
							aura = auraList[check.unitType][2]  -- FILTER the list, then
						else
							aura = auraList[check.unitType][1]  -- remove this else statement
						end

						
						--Let's see if we have to refresh it or not
						local refresh = false
						if (check.refreshAura) then

							if (aura[check.name] ~= nil) then
								if (aura[check.name].timeLeft ~= 0 and aura[check.name].timeLeft 
								 <= castTime + check.refreshDuration + leeway) then
									refresh = true
								elseif (aura[check.name].timeLeft ~= 0 and aura[check.name].timeLeft 
									< castTime + check.refreshDuration) then
									refresh = true
									earlyCast = true	
								end
							end
						end
						

						--If it shouldn't be refreshed, check if it does not fall between the aura stack reqs..
						if (not refresh) then
							local stack = 0
							if (aura[check.name] ~= nil) then
								stack = aura[check.name].count
							end
							
							--See if we are casting the spell, and see if it applies the stack...
							if (check.spellTriggers ~= "Disabled") then
								if (self.spellInfo[v.spellName] ~= nil and self.spellInfo[v.spellName].endCast ~= nil and self.spellInfo[v.spellName].endCast > self.time) then
									if (check.spellTriggers == "Add") then
										stack = stack + 1
									else
										stack = stack - 1
									end
								elseif (currentlyCastingSpell == v.spellName or currentlyCastingPetSpell == v.spellName) then
									if (check.spellTriggers == "Add") then
										stack = stack + 1
									else
										stack = stack - 1
									end
								end
							end
							
							--Lastly see if the spell matches the aura stack reqs
							if (stack < check.minStack or stack > check.maxStack) then
								pass = false
							end
					end
					
					 -- TODO make this more modular and more efficient ..
					elseif (check.type == "status") then
						if (check.health and check.health ~= "Disabled") then
							local percent = UnitHealth(check.unitType) / UnitHealthMax(check.unitType)
							if ((percent < check.healthAmount) ~= (check.health == "Less than")) then
								pass = false
							end
						end
						if (check.power and check.power ~= "Disabled") then
							local percent = UnitMana(check.unitType) / UnitManaMax(check.unitType)
							if ((percent < check.powerAmount) ~= (check.power == "Less than")) then
								pass = false
							end
						end

						--combo point check only if enabled and toon has combo points
						-- will put in unitType as an argument in 3.0
						if (check.combo and check.combo ~= "Disabled") then
							if ((GetComboPoints() < check.comboPoints) ~= (check.combo == "Less than")) then
								pass = false
							end
						end
						 
						 
					elseif (check.type == "itemReady") then
						local start, duration = GetItemCooldown(check.name)
						local localPass = true
						if (start == nil) then
							localPass = false
						elseif (start ~= 0) then 
							local left = start + duration - GetTime()
							if (left > leeway) then
								localPass = false
							elseif (left > 0) then
								earlyCast = true
							end
						end
						if (localPass ~= check.ready) then
							pass = false
						end
					elseif (check.type == "equipmentReady") then
						local itemName = check.name
						local _,_,found = strfind(check.name, "^Slot(%d)+")
						local localPass = true
						if (found ~= nil) then
							itemName = self.playerEquipment[found]
						end
						if (check.name ~= "TopTrinket" and check.name ~= "BottomTrinket" and check.name ~= "OneTrinket" and check.name ~= "BothTrinkets") then
							
							--Check if equipped first
							if (IsEquippedItem(itemName) ~= 1) then
								localPass = false
							else
								local start, duration = GetItemCooldown(itemName)
								if (start == nil) then
									localPass = false
								elseif (start ~= 0) then 
									local left = start + duration - GetTime()
									if (left > leeway) then
										localPass = false
									elseif (left > 0) then
										earlyCast = true
									end
								end
							end
						else
						
							local topTrinket, bottomTrinket = true, true
							local topEarlyCast, bottomEarlyCast = false, false
							if (self.playerEquipment[13] == nil) then
								topTrinket = false;
							else
								local start, duration = GetItemCooldown(self.playerEquipment[13])
								if (start == nil) then
									topTrinket = false;
								elseif (start ~= 0) then
									local left = start + duration - GetTime()
									if (left > leeway) then
										topTrinket = false
									elseif (left > 0) then
										topEarlyCast = true
									end
								end
							end
							if (self.playerEquipment[14] == nil) then
								bottomTrinket = false;
							else
								local start, duration = GetItemCooldown(self.playerEquipment[14])
								if (start == nil) then
									bottomTrinket = false;
								elseif (start ~= 0) then
									local left = start + duration - GetTime()
									if (left > leeway) then
										bottomTrinket = false
									elseif (left > 0) then
										bottomEarlyCast = true
									end
								end
							end
							
							if (check.name == "TopTrinket") then
								localPass = topTrinket
								if (topEarlyCast) then earlyCast = true end
							elseif (check.name == "BottomTrinket") then
								localPass = bottomTrinket
								if (bottomEarlyCast) then earlyCast = true end
							elseif (check.name == "OneTrinket") then
								localPass = topTrinket or bottomTrinket
								if (topEarlyCast and bottomEarlyCast) then earlyCast = true end
							else
								localPass = topTrinket and bottomTrinket
								if (topEarlyCast or bottomEarlyCast) then earlyCast = true end
							end
						end
						if (localPass ~= check.ready) then
							pass = false
						end
					elseif (check.type == "spellReady") then
						if ((check.name == currentlyCastingPlayerSpell or check.name == currentlyCastingPetSpell) and self.spellInfo[check.name] ~= nil and self.spellInfo[check.name].cooldown > 0) then
							local localPass = true
							if (localPass ~= check.ready) then
								pass = false
							end
						else
							local start, duration = GetSpellCooldown(check.name, "spell")
							if (start == nil) then
								start, duration = GetSpellCooldown(check.name, "pet")
							end
							local localPass = true
							if (start == nil) then
								localPass = false
							elseif (start ~= 0) then
								local left = start + duration - GetTime()
								if (left > leeway + myGCD) then
									localPass = false
								end
								if (left > 0) then
									earlyCast = true
								end
							end
							if (localPass ~= check.ready) then
								pass = false
							end
						end
					end
					
					if (not pass) then
						break
					end
				end
				
			
				if (pass and v.spellName ~= "" and self.spellInfo[v.spellName] ~= nil) then
					if ((v.spellName == currentlyCastingPlayerSpell or
						 v.spellName == currentlyCastingPetSpell)
						and v.ignoreCooldown == false
						and self.spellInfo[v.spellName].cooldown > 0)
						then
						pass = false
					else
						--TODO Optimisation: CD check first, then condition check DUH
						-- modification : generalize GCD
						local start, duration = GetSpellCooldown(v.spellName, "spell")
						
						if (start == nil) then
							start,duration = GetSpellCooldown(v.spellName, "pet")
						end
						if (start ~= 0) then 
							local left = start + duration - GetTime()
							if (v.ignoreCooldown == false and left > leeway + myGCD) then
								pass = false
							end
							if (v.ignoreCooldown == false and left > 0) then
								--Who the fuck cares
								earlyCast = true
							end
						end
					end
				end

				if (pass) then
					self.refreshList[count].name = v.name
					self.refreshList[count].spellName = v.spellName
					self.refreshList[count].castTime = castTime
					self.refreshList[count].earlyCast = earlyCast
					self.refreshList[count].nextCast = nextCast
					self.refreshList[count].priority = v.priority
					if (v.texture ~= "" and v.texture ~= nil) then
						--An item was suggested? Or maybe a texture path. Let's just test the item texture...
						local _,_,_,_,_,_,_,_,_,texture = GetItemInfo(v.texture)
						if (texture ~= nil) then
							self.refreshList[count].texture = texture
						elseif (self.spellInfo[v.texture] ~= nil) then
							self.refreshList[count].texture = self.spellInfo[v.texture].texture
						else
							self.refreshList[count].texture = v.texture
						end
					else
						if (self.spellInfo[v.spellName] ~= nil) then
							self.refreshList[count].texture = self.spellInfo[v.spellName].texture
						else
							self.refreshList[count].texture = nil
						end
					end
					

					--[[
					if (v.spellName ~= "") then
						if (earlyCast) then
							--Eh it's buggy.. maybe some other time..
							self.SpellFrame:Add(v.name, self.SpellInfo[v.spellName].texture)
						else
							self.SpellFrame:Add(v.name, self.SpellInfo[v.spellName].texture)
						end
					else
						self.SpellFrame:Add(v.name)
					end]]
				else
					count = count - 1
				end
			end
		end
		
		if (count > 0) then
			local iterator = newList()
			for i=1,count do
				tinsert(iterator, {i,self.refreshList[i].priority})
			end
			sort(iterator, function(a,b) return a[2] > b[2] end)
			if (count > 1 and self.loopError == nil) then
				-- I pray for no infinite loops --
				local loopCount = 0
				while (true) do
					loopCount = loopCount + 1
					if (loopCount == 100) then
						self.loopError = true
						message("Training Wheels Loop Error! Refresh List is disabled. Please report this error!")
						break
					end
					local found = false
					local finishTime = max(self.refreshList[iterator[1][1]].castTime, myGCD)
					
					for i=2, count do
						if self.refreshList[iterator[i][1]].nextCast < finishTime then
							--Swap swap swap
							for j=i,2,-1 do
								if self.refreshList[iterator[j][1]].nextCast <
										self.refreshList[iterator[j-1][1]].nextCast then
									iterator[j], iterator[j - 1] = iterator[j - 1], iterator[j]
									found = true
								else
									break
								end
							end
						end
					end
					
					if not found then
						break
					end
				end
			end
			
			--Loop through each spell
			for i,stuff in pairs(iterator) do
				v = self.refreshList[stuff[1]]
				if (v.earlyCast) then
					self.SpellFrame:Add(v.name, v.texture, "yellow")
				else
					self.SpellFrame:Add(v.name, v.texture)
				end
			end
			
			del(iterator) -- shouldnt this be iterator = del(iterator) ?
		end
		
		del(auraList) -- shouldnt this be auraList = del(auraList) ?
end

-- TO DO

-- function self:AuraCheck(check, auraList, castTime, leeway, currentlyCastingPlayerSpell, currentlyCastingPetSpell)
-- Arguments
-- 		check         : is a check of type aura
-- 		auraList      : initialized AuraTable
-- 		castTime      : initialized number >= 0
-- 		leeway        : initialized number >= 0
-- 		currentlyCastingPlayerSpell : not used
-- 		currentlyCastingPetSpell    : not used
-- Returns 			  : true if the check for the AuraCheck is met, false otherwise
-- function self:AuraCheck(check, auraList, castTime, leeway, currentlyCastingPlayerSpell, currentlyCastingPetSpell)
--		local aura = {}
--		setmetatable(aura, { _mode="kv"})
--		if (check.playerCastOnly) then
--			aura = auraList[check.unitType][2]  -- FILTER the list, then
--		else
--			aura = auraList[check.unitType][1]  -- remove this else statement
--		end
--
		--Let's see if we have to refresh it or not
--		local refresh = false
--		if (check.refreshAura) then
--			if (aura[check.name] ~= nil) then
--					if (aura[check.name].timeLeft ~= 0 and aura[check.name].timeLeft
--						<= castTime + check.refreshDuration + leeway)
--					then
--						refresh = true
--					elseif (aura[check.name].timeLeft ~= 0 and aura[check.name].timeLeft
--							< castTime + check.refreshDuration)
--					then
--						refresh = true
--						earlyCast = true
--					end
--			end
--		end
--
--
--		--If it shouldn't be refreshed, check if it does not fall between the aura stack reqs..
--		if (not refresh) then
--			local stack = 0
--			if (aura[check.name] ~= nil) then
--				stack = aura[check.name].count
--			end
--							
--			--See if we are casting the spell, and see if it applies the stack...
--			if (check.spellTriggers ~= "Disabled") then
--				if (self.spellInfo[v.spellName] and self.spellInfo[v.spellName].endCast and self.spellInfo[v.spellName].endCast > self.time)
--				then
--					if (check.spellTriggers == "Add") then
--						stack = stack + 1
--					else
--						stack = stack - 1
--					end
--				elseif (currentlyCastingSpell == v.spellName or currentlyCastingPetSpell == v.spellName)
--				then
--					if (check.spellTriggers == "Add") then
--						stack = stack + 1
--					else
--						stack = stack - 1
--					end
--				end
--			end
--							
--			--Lastly see if the spell matches the aura stack reqs
--			if (stack < check.minStack or stack > check.maxStack) then
--    			return false
--			end
--		end
--  	return true
--  end
--
-- function self:StatusCheck(check, auraList, castTime, leeway, currentlyCastingPlayerSpell, currentlyCastingPetSpell)
-- Arguments
-- 		check         : is a check of type status
-- 		auraList      : initialized AuraTable
-- 		castTime      : not used
-- 		leeway        : not used
-- 		currentlyCastingPlayerSpell : not used
-- 		currentlyCastingPetSpell    : not used
-- Returns 			  : true if the check for the StatusCheck is met, false otherwise
-- function self:StatusCheck(check, auraList, castTime, leeway, currentlyCastingPlayerSpell, currentlyCastingPetSpell)

--						if (check.health and check.health ~= "Disabled") then
--							local percent = UnitHealth(check.unitType) / UnitHealthMax(check.unitType)
--							if ((percent < check.healthAmount) ~= (check.health == "Less than")) then
--        						return false
--							end
--						end

--						if (check.power and check.power ~= "Disabled") then
--							local percent = UnitMana(check.unitType) / UnitManaMax(check.unitType)
--							if ((percent < check.powerAmount) ~= (check.power == "Less than")) then
--                              return false
--							end
--						end
--
--						--combo point check only if enabled and toon has combo points
--						-- will put in unitType as an argument in 3.0
--						if (check.combo and check.combo ~= "Disabled") then
--							if ((GetComboPoints() < check.comboPoints) ~= (check.combo == "Less than")) then
--								return false
--							end
--                      end
--
--   add threat status check

--   return true
-- end

-- function self:ItemReadyCheck(check, auraList, castTime, leeway, currentlyCastingPlayerSpell, currentlyCastingPetSpell)
-- Arguments
-- 		check         : is a check of type itemReady
-- 		auraList      : not used
-- 		castTime      : not used
-- 		leeway        : not used
-- 		currentlyCastingPlayerSpell : not used
-- 		currentlyCastingPetSpell    : not used
-- Returns 			  : true if the check for the StatusCheck is met, false otherwise
-- function self:ItemReadyCheck(check, auraList, castTime, leeway, currentlyCastingPlayerSpell, currentlyCastingPetSpell)
--
--		local start, duration = GetItemCooldown(check.name)
--		local localPass = true
--		if (start == nil) then
--			localPass = false
--		elseif (start ~= 0) then
--			local left = start + duration - GetTime()
--			if (left > leeway) then
--				localPass = false
--			end
--		end
--
--      return localPass == check.ready
-- end

-- function self:EquipmentReadyCheck(check, auraList, castTime, leeway, currentlyCastingPlayerSpell, currentlyCastingPetSpell)
-- Arguments
-- 		check         : is a check of type itemReady
-- 		auraList      : not used
-- 		castTime      : not used
-- 		leeway        : not used
-- 		currentlyCastingPlayerSpell : not used
-- 		currentlyCastingPetSpell    : not used
-- Returns 			  : true if the check for the EquipmentReadyCheck is met, false otherwise
-- function self:EquipmentReadyCheck(check, auraList, castTime, leeway, currentlyCastingPlayerSpell, currentlyCastingPetSpell)
--		local itemName = check.name
--		local _,_,found = strfind(check.name, "^Slot(%d)+")
--		local localPass = true
--		if (found ~= nil) then
--			itemName = self.playerEquipment[found]
--		end
--		if (check.name ~= "TopTrinket"
--			and check.name ~= "BottomTrinket"
--			and check.name ~= "OneTrinket"
-- 			and check.name ~= "BothTrinkets")
--		then
--			--Check if equipped first
--			if (IsEquippedItem(itemName) ~= 1) then
--				localPass = false
--			else
--				local start, duration = GetItemCooldown(itemName)
--				if (start == nil) then
--					localPass = false
--				elseif (start ~= 0) then
--					local left = start + duration - GetTime()
--					if (left > leeway) then
--						localPass = false
--					elseif (left > 0) then
--						earlyCast = true
--					end
--				end
--			end
--		else
--						
--			local topTrinket, bottomTrinket = true, true
--			local topEarlyCast, bottomEarlyCast = false, false
--			if (self.playerEquipment[13] == nil) then
--				topTrinket = false
--			else
--				local start, duration = GetItemCooldown(self.playerEquipment[13])
--				if (start == nil) then
--					topTrinket = false
--				elseif (start ~= 0) then
--					local left = start + duration - GetTime()
--					if (left > leeway) then
--						topTrinket = false
--						elseif (left > 0) then
--							topEarlyCast = true
--					end
--				end
--			end
--			if (self.playerEquipment[14] == nil) then
--				bottomTrinket = false
--			else
--				local start, duration = GetItemCooldown(self.playerEquipment[14])
--				if (start == nil) then
--					bottomTrinket = false
--				elseif (start ~= 0) then
--					local left = start + duration - GetTime()
--					if (left > leeway) then
--						bottomTrinket = false
--					elseif (left > 0) then
--						bottomEarlyCast = true
--					end
--				end
--			end
--							
--			if (check.name == "TopTrinket") then
--				localPass = topTrinket
--				elseif (check.name == "BottomTrinket") then
--					localPass = bottomTrinket
--				elseif (check.name == "OneTrinket") then
--					localPass = topTrinket or bottomTrinket
--				else
--					localPass = topTrinket and bottomTrinket
--				end
--			end
--
--      return localPass == check.ready
-- end

-- function self:SpellReadyCheck(check, auraList, castTime, leeway, currentlyCastingPlayerSpell, currentlyCastingPetSpell)
-- Arguments
-- 		check         : is a check of type spellReady
-- 		auraList      : not used
-- 		castTime      : not used
-- 		leeway        : not used
-- 		currentlyCastingPlayerSpell : initialized string
-- 		currentlyCastingPetSpell    : initialized string
-- Returns 			  : true if the check for the SpellReadyCheck is met, false otherwise
-- function self:SpellReadyCheck(check, auraList, castTime, leeway, currentlyCastingPlayerSpell, currentlyCastingPetSpell)
--      local localPass = true
--
--		if ( (check.name == currentlyCastingPlayerSpell or check.name == currentlyCastingPetSpell)
--			and self.spellInfo[check.name] and self.spellInfo[check.name].cooldown > 0)
-- 		then
--			local start, duration = GetSpellCooldown(check.name, "spell")
--			if (start == nil) then
--				start, duration = GetSpellCooldown(check.name, "pet")
--			end
--			local localPass = true
--			if (start == nil) then
--				localPass = false
--			elseif (start ~= 0) then
--				local left = start + duration - GetTime()
--				if (left > leeway + self.myGCD) then
--					localPass = false
--				end
--				if (left > 0) then
--					earlyCast = true
--				end
--			end
--  	end
--      return localPass == check.ready
-- end




function self:ScheduleRefreshSpellFrame()
	self.nextFrameRefresh = true
	
	if self.DEBUG then
	 	self:Print("Scheduling refresh spell frame at time - ")
	 	self:Print(GetTime())
	 end
	
end

function self:ListEquipmentSlots()
	local output = "Listing Equipment Slots"
	for i=0,19 do
		local link = GetInventoryItemLink("player", i)
		if link == nil then
			output = output .. "\nSlot "..i..": Empty"
		else
			local itemName = GetItemInfo(link)
			output = output .. "\nSlot "..i..": "..itemName
		end
	end
	self:Print(output)
end

--Get's the Auras for a unit
-- Arguments: valid unitID string
-- Returns  : table of buffs and debuffs
-- TO DO  Improve modularity
function self:GetAuras(unit)
	
	local allAuras = self:GetAurasHelper(unit, "HELPFUL",{}, false) 
	allAuras =       self:GetAurasHelper(unit, "HARMFUL",allAuras, false)
	setmetatable(allAuras, { _mode="kv"})
	
	local playerCastedAuras = self:GetAurasHelper(unit, "HELPFUL",{}, true)
	playerCastedAuras	    = self:GetAurasHelper(unit, "HARMFUL",playerCastedAuras , true)
	setmetatable(playerCastedAuras, { _mode="kv"})
	
	return  allAuras, playerCastedAuras
end

-- GetAurasHelper(unit, AuraType)
-- unit is a unitID
-- filter is a string which is valid filter : HELPFUL, HARMFUL, PLAYER, RAID, CANCELABLE, NOT_CANCELABLE
-- filter also be a string of valid filters, delimited by |
-- AuraList is a table
-- 
-- Returns list of buffs or debuffs for unit, extending AuraList, depending on UnitAurasFcn
--  if playerCast is not false or nil, then only includes playerCast spells
-- Side Effects - Will overwrite AuraList if there are conflicts
-- ToDo : Make this a stateless iterator, maybe cache functions locally
function self:GetAurasHelper(unit, filter, AuraList, playerCast)

  local DEFAULT_TIME = 99
--  loop
	local i = 0
	while true do                         -- TO DO change to for loop
		i = i + 1
		local name,_,_,count,type,duration,timeLeft,_,_ = UnitAura(unit, i, filter)
		
		if (name == nil) then break end
		
--			if (self.DEBUG and i < 10) then
--    		self:Print("Added entry for name " .. name .. " count i" .. count .. " " .. i)
--			end


		-- if duration and timeLeft are not nil, then aura was player cast
		
		-- if playerCast -> duration then process, otherwise skip
		if (not playerCast or duration) then
			if (duration == nil) then duration = DEFAULT_TIME end
			if (timeLeft == nil) then timeLeft = DEFAULT_TIME end
		    if (type  == nil and filter == HELPFUL) then type = "Buff" end
		
			if (count == 0) then count = 1 end
			if (type == "" or type == nil) then type = "No Type" end
			AuraList[name] = newDict("name", name, "count", count, "type", type, 
									"duration", duration, "timeLeft", timeLeft)
			setmetatable(AuraList[name], { _mode="kv"})
			
			if (AuraList[type] == nil) then
				AuraList[type] = newDict("name", type, "count", count, "type", type, 
									"duration", duration, "timeLeft", timeLeft)
				setmetatable(AuraList[type], { _mode="kv"})					
				
			else
				local myAuraListType =   AuraList[type]   -- store to minimize overhead
	
				myAuraListType.duration = min(myAuraListType.duration, duration)
				myAuraListType.timeLeft = min(myAuraListType.timeLeft, timeLeft)
				myAuraListType.count = myAuraListType.count + count
			end
			
		end   -- end playerCast => duration block
	end    -- end while loop

	return AuraList
end     -- end function

function self:OnClick(button)
	if (button == "LeftButton") then
		self:OpenConfigMenu()
	else
		self:OpenMenu()
	end
end

function self:OnTooltipUpdate()
	GameTooltip:AddLine("|cffffff00 Click|r to configure Training Wheels.\n|cffffff00 Right-Click|r to set FuBar options.")
end

function self:ShowVersion()
	message("Training Wheels "..self.version)
end

--Clone a table
function self:CloneTable(value, max, counter)
	if (type(value) ~= "table") then
		return false, "Invalid type."
	end
	if (max ~= nil and type(max) == "number" and max > 0) then
		if (counter == nil) then
			counter = 0
		end
		counter = counter + #value
		if (counter > max) then
			return false, "Too many elements."
		end
	else
		max = nil
	end
	
	local output = {}
	for i,v in pairs(value) do
		if (type(v) ~= "table") then
			--No executable bullshit
			if (type(v) == "function" or type(v) == "thread" or type(v) == "userdata") then
				return false, "Invalid data type "..type(v) .. "."
			end
			output[i] = v
		else
			local result,msg =  self:CloneTable(v, max, counter)
			if (not result) then
				return false, msg
			else
				output[i] = result
			end
		end
	end
	return output
end

-- Takes a string argument s
-- returns true iff val is both non empty and has no leading, tailing whitespace
function self.EmptyOrWhiteSpace(v)
	return (v == "") or (v ~= strtrim(v))
end

-- Arguments
-- f is a boolean function that takes 1 argument
-- t is a table
-- Result
-- subset of t consisting of the pairs (k,v) where f(v) is true
function self.filter(f, table)
  -- assert(type(func)== "function" and type(table) == "table")
 
  local result = {}
  for k, v in pairs(table) do
    if func(v) then
      result[k] = v
    end
  end
 
  return result
end

-- Arguments
-- f is a boolean function that takes 1 argument
-- t is a table
-- Result
-- returns true, k,v where (k,v) is in table and f(v) is true
-- otherwise returns nil
function self.exists(f, table)
	-- assert(type(func)== "function" and type(table) == "table")
	
	for k, v in pairs(table) do
		if (f(v)) then return true,k,v end
	end	

	return nil
end

-- Arguments
-- f is a boolean function that takes 1 argument
-- t is a table
-- Result
-- returns true if  f(v) is true for all (k,v) in table
-- otherwise returns false, k, v for some f(v) which is false
function self.forall(f, table)
	-- assert(type(func)== "function" and type(table) == "table")
	
	for k, v in pairs(table) do
		if (not f(v)) then return false, k,v end
	end	

	return true
end


-- merge: Merge two tables
-- If there are duplicate fields, u's will be used.
--   t, u: tables
-- returns
--   r: the merged table
function self.merge(t, u)
  local r = {}
  for i, v in t do
    r[i] = v
  end
  for i, v in u do
    r[i] = v
  end

  return r
end